"""
Notice : 神兽保佑 ，测试一次通过
//      
//      ┏┛ ┻━━━━━┛ ┻┓
//      ┃　　　　　　 ┃
//      ┃　　　━　　　┃
//      ┃　┳┛　  ┗┳　┃
//      ┃　　　　　　 ┃
//      ┃　　　┻　　　┃
//      ┃　　　　　　 ┃
//      ┗━┓　　　┏━━━┛
//        ┃　　　┃   Author: somewheve
//        ┃　　　┃   Datetime: 2019/7/3 下午8:46  ---> 无知即是罪恶
//        ┃　　　┗━━━━━━━━━┓
//        ┃　　　　　　　    ┣┓
//        ┃　　　　         ┏┛
//        ┗━┓ ┓ ┏━━━┳ ┓ ┏━┛
//          ┃ ┫ ┫   ┃ ┫ ┫
//          ┗━┻━┛   ┗━┻━┛
//
"""

""" 本地持仓对象 """

from copy import copy

from ctpbee.constant import PositionData, Offset, Direction, OrderRequest, OrderData, \
    Exchange, TickData


class LocalVariable:
    def __init__(self, data):
        try:
            if data.get("long") is not None:
                self.long = float(data.get('long'))
        except TypeError:
            self.long = 0
        else:
            self.long = 0
        if data.get("short") is not None:
            try:
                self.short = float(data.get('long'))
            #todo: error solve
            except TypeError:
                self.short = 0
        else:
            self.short = 0


# local position support


class PositionHolding:
    """ 单个合约的持仓 """

    def __init__(self, local_symbol, app):
        """"""
        self.local_symbol = local_symbol
        try:
            self.exchange = local_symbol.split(".")[1]
            self.symbol = local_symbol.split(".")[0]
        except Exception:
            raise ValueError("invalid local_symbol")
        self.active_orders = {}
        self.size = 1
        if app.recorder.get_contract(self.local_symbol) is not None:
            self.size = app.recorder.get_contract(self.local_symbol).size

        self.app = app
        self.long_pos = 0
        self.long_yd = 0
        self.long_td = 0
        self.long_pnl = 0
        self.long_price = 0

        self.short_pos = 0
        self.short_yd = 0
        self.short_td = 0
        self.short_pnl = 0
        self.short_price = 0

        self.long_pos_froze = 0
        self.long_yd_frozen = 0
        self.long_td_frozen = 0

        self.short_pos_frozen = 0
        self.short_yd_frozen = 0
        self.short_td_frozen = 0

        self.last_price = 0

    def update_trade(self, trade):
        """成交更新"""
        # 多头
        if trade.direction == Direction.LONG:
            # 开仓
            if trade.offset == Offset.OPEN:
                self.long_td += trade.volume
            # 平今
            elif trade.offset == Offset.CLOSETODAY:
                self.short_td -= trade.volume
            # 平昨
            elif trade.offset == Offset.CLOSEYESTERDAY:
                self.short_yd -= trade.volume
            # 平仓
            elif trade.offset == Offset.CLOSE:
                # 上期所等同于平昨
                if self.exchange == Exchange.SHFE:
                    self.short_yd -= trade.volume
                # 非上期所，优先平今
                else:
                    self.short_td -= trade.volume

                    if self.short_td < 0:
                        self.short_yd += self.short_td
                        self.short_td = 0
                        # 空头
        elif trade.direction == Direction.LONG:
            # 开仓
            if trade.offset == Offset.OPEN:
                self.short_td += trade.volume
            # 平今
            elif trade.offset == Offset.CLOSETODAY:
                self.long_td -= trade.volume
            # 平昨
            elif trade.offset == Offset.CLOSEYESTERDAY:
                self.long_yd -= trade.volume
            # 平仓
            elif trade.offset == Offset.CLOSE:
                # 上期所等同于平昨
                if self.exchange == Exchange.SHFE:
                    self.long_yd -= trade.volume
                # 非上期所，优先平今
                else:
                    self.long_td -= trade.volume

                    if self.long_td < 0:
                        self.long_yd += self.long_td
                        self.long_td = 0

        # 汇总
        self.calculate_price(trade)
        self.calculate_position()
        self.calculate_pnl()

    def calculate_position(self):
        """计算持仓情况"""
        self.long_pos = self.long_td + self.long_yd
        self.short_pos = self.short_td + self.short_yd

    def update_position(self, position: PositionData):
        """"""
        if position.direction == Direction.LONG:
            self.long_pos = position.volume
            self.long_yd = position.yd_volume
            self.long_td = self.long_pos - self.long_yd
            self.long_pnl = position.pnl
            self.long_price = position.price

        elif position.direction == Direction.SHORT:
            self.short_pos = position.volume
            self.short_yd = position.yd_volume
            self.short_td = self.short_pos - self.short_yd
            self.short_pnl = position.pnl
            self.short_price = position.price


    def update_order(self, order: OrderData):
        """"""
        if order._is_active():
            self.active_orders[order.local_order_id] = order
        else:
            if order.local_order_id in self.active_orders:
                self.active_orders.pop(order.local_order_id)

        self.calculate_frozen()

    def update_order_request(self, req: OrderRequest, local_order_id: str):
        """"""
        gateway_name, orderid = local_order_id.split(".")

        order = req.create_order_data(orderid, gateway_name)
        self.update_order(order)

    def update_tick(self, tick):
        """行情更新"""
        self.last_price = tick.last_price
        self.calculate_pnl()

    def calculate_frozen(self):
        """"""
        self.long_pos_frozen = 0
        self.long_yd_frozen = 0
        self.long_td_frozen = 0

        self.short_pos_frozen = 0
        self.short_yd_frozen = 0
        self.short_td_frozen = 0

        for order in self.active_orders.values():
            # Ignore position open orders
            if order.offset == Offset.OPEN:
                continue

            frozen = order.volume - order.traded

            if order.direction == Direction.LONG:
                if order.offset == Offset.CLOSETODAY:
                    self.short_td_frozen += frozen
                elif order.offset == Offset.CLOSEYESTERDAY:
                    self.short_yd_frozen += frozen
                elif order.offset == Offset.CLOSE:
                    self.short_td_frozen += frozen

                    if self.short_td_frozen > self.short_td:
                        self.short_yd_frozen += (self.short_td_frozen
                                                 - self.short_td)
                        self.short_td_frozen = self.short_td
            elif order.direction == Direction.SHORT:
                if order.offset == Offset.CLOSETODAY:
                    self.long_td_frozen += frozen
                elif order.offset == Offset.CLOSEYESTERDAY:
                    self.long_yd_frozen += frozen
                elif order.offset == Offset.CLOSE:
                    self.long_td_frozen += frozen

                    if self.long_td_frozen > self.long_td:
                        self.long_yd_frozen += (self.long_td_frozen
                                                - self.long_td)
                        self.long_td_frozen = self.long_td

            self.long_pos_frozen = self.long_td_frozen + self.long_yd_frozen
            self.short_pos_frozen = self.short_td_frozen + self.short_yd_frozen

    def convert_order_request_shfe(self, req: OrderRequest):
        """"""
        if req.offset == Offset.OPEN:
            return [req]

        if req.direction == Direction.LONG:
            pos_available = self.short_pos - self.short_pos_frozen
            td_available = self.short_td - self.short_td_frozen
        else:
            pos_available = self.long_pos - self.long_pos_frozen
            td_available = self.long_td - self.long_td_frozen

        if req.volume > pos_available:
            return []
        elif req.volume <= td_available:
            req_td = copy(req)
            req_td.offset = Offset.CLOSETODAY
            return [req_td]
        else:
            req_list = []

            if td_available > 0:
                req_td = copy(req)
                req_td.offset = Offset.CLOSETODAY
                req_td.volume = td_available
                req_list.append(req_td)

            req_yd = copy(req)
            req_yd.offset = Offset.CLOSEYESTERDAY
            req_yd.volume = req.volume - td_available
            req_list.append(req_yd)

            return req_list

    def convert_order_request_lock(self, req: OrderRequest):
        """"""
        if req.direction == Direction.LONG:
            td_volume = self.short_td
            yd_available = self.short_yd - self.short_yd_frozen
        else:
            td_volume = self.long_td
            yd_available = self.long_yd - self.long_yd_frozen

        # If there is td_volume, we can only lock position
        if td_volume:
            req_open = copy(req)
            req_open.offset = Offset.OPEN
            return [req_open]
        # If no td_volume, we close opposite yd position first
        # then open new position
        else:
            open_volume = max(0, req.volume - yd_available)
            req_list = []

            if yd_available:
                req_yd = copy(req)
                if self.exchange == Exchange.SHFE:
                    req_yd.offset = Offset.CLOSEYESTERDAY
                else:
                    req_yd.offset = Offset.CLOSE
                req_list.append(req_yd)

            if open_volume:
                req_open = copy(req)
                req_open.offset = Offset.OPEN
                req_open.volume = open_volume
                req_list.append(req_open)

            return req_list

    def calculate_pnl(self):
        """计算持仓盈亏"""
        try:
            open_cost = self.app.trader.open_cost_dict.get(self.symbol)
            single = LocalVariable(open_cost)
        except AttributeError as e:
            single = None
        try:

            if self.long_pos == self.long_yd:
                self.long_pnl = self.long_pos * (
                        self.last_price - single.long / (self.size * self.long_pos)) * self.size

            if self.long_pos != self.long_yd:
                self.long_pnl = self.long_pos * (self.last_price - self.long_price) * self.size
        except ZeroDivisionError:
            self.long_pnl = 0
        except AttributeError:
            self.long_pnl = 0
        try:
            if self.short_pos == self.short_yd:
                self.short_pnl = self.short_pos * (single.short / (self.size * self.short_pos) - self.last_price) * self.size
            if self.short_pos != self.short_yd:
                self.short_pnl = self.short_pos * (self.short_price - self.last_price) * self.size
        except ZeroDivisionError:
            self.short_pnl = 0
        except AttributeError:
            self.short_pnl = 0

    def calculate_price(self, trade):
        """计算持仓均价（基于成交数据）"""
        # 只有开仓会影响持仓均价
        if trade.offset == Offset.OPEN:
            if trade.direction == Direction.LONG:
                cost = self.long_price * self.long_pos
                cost += trade.volume * trade.price
                new_pos = self.long_pos + trade.volume
                if new_pos:
                    self.long_price = cost / new_pos
                else:
                    self.long_price = 0
            else:
                cost = self.short_price * self.short_pos
                cost += trade.volume * trade.price
                new_pos = self.short_pos + trade.volume
                if new_pos:
                    self.short_price = cost / new_pos
                else:
                    self.short_price = 0


class LocalPositionManager(dict):
    """ 用于管理持仓信息 只提供向外的接口 """

    def __init__(self, app):
        super().__init__({})
        self.app = app

    def update_tick(self, tick: TickData):
        """ 更新tick  """
        if tick.local_symbol not in self:
            return
        self.get(tick.local_symbol).update_tick(tick)

    def update_order(self, order):
        """ 更新order """
        if order.local_symbol not in self:
            self[order.local_symbol] = PositionHolding(order.local_symbol, self.app)
        else:
            self.get(order.local_symbol).update_order(order)

    def update_trade(self, trade):
        """ 更新成交  """
        if trade.local_symbol not in self:
            return
        self.get(trade.local_symbol).update_trade(trade)

    def update_position(self, position):
        """ 更新持仓 """
        if position.local_symbol not in self.keys():
            self[position.local_symbol] = PositionHolding(position.local_symbol, self.app)
            self[position.local_symbol].update_position(position)
        else:
            self.get(position.local_symbol).update_position(position)

    def get_position(self, local_position_id):
        """ 根据local_position_id 获取持仓信息 """
        return self.get(local_position_id, None)

    def get_all_positions(self):
        """ 返回所有的持仓信息 """
        position_list = []
        for x in self.values():
            if x.symbol == "":
                continue
            if x.long_pos != 0:
                temp = {}
                temp['exchange'] = x.exchange
                temp['direction'] = "long"
                temp['position_profit'] = x.long_pnl
                temp['symbol'] = x.symbol
                temp['local_symbol'] = x.local_symbol
                temp['price'] = x.long_price
                temp['volume'] = x.long_pos
                temp['yd_volume'] = x.long_yd
                if x.long_pos == x.long_yd:
                    temp['position_date'] = 2
                elif x.long_pos != x.long_yd:
                    temp['position_date'] = 1
                position_list.append(temp)
            if x.short_pos != 0:
                temp = {}
                temp['exchange'] = x.exchange
                temp['direction'] = "short"
                temp['position_profit'] = x.short_pnl
                temp['symbol'] = x.symbol
                temp['local_symbol'] = x.local_symbol
                temp['price'] = x.short_price
                temp['volume'] = x.short_pos
                temp['yd_volume'] = x.short_yd
                if x.short_pos == x.short_yd:
                    temp['position_date'] = 2
                elif x.short_pos != x.short_yd:
                    temp['position_date'] = 1
                position_list.append(temp)
        return position_list

    @property
    def length(self):
        return len(self)
